/*******************************************************\
|* Author: Trevor Sundberg
\*******************************************************/

// Includes
#include "SkugoRtti.hpp"
#include "SkugoBuffer.hpp"

namespace Skugo
{
  // Default constructor
  RuntimePointer::RuntimePointer()
  {
    // Initialize to safe defaults
    Instance = NULL;
    Type = NULL;
  }

  // Default constructor
  MemberInfo::MemberInfo()
  {
    // Initialize to safe defaults
    Type = NULL;
    Get = NULL;
    Set = NULL;
  }

  // Default constructor
  MethodInfo::MethodInfo()
  {
    // Initialize to safe defaults
    Function = NULL;
    ThisType = NULL;
  }

  // Construct a derived class type
  TypeInfo::TypeInfo()
  {
    // Initialize to safe defaults
    TypeGuid = InvalidGuid;
    PrimitiveSerializer = NULL;
    Destructor = NULL;
    Size = 0;
    Parent = NULL;
  }

  namespace PrimitiveGuid
  {
    enum Enum
    {
      SByte,
      Int16,
      Int32,
      Int64,
      Byte,
      UInt16,
      UInt32,
      UInt64,
      Single,
      Double,
      Boolean,
      String
    };
  }

  namespace PrimitiveTrait
  {
    enum Enum
    {
      SignedIntegral,
      UnsignedIntegral,
      FloatingPoint,
      Boolean
    };
  }

  // Setup a string type (the serializer must be provided)
  void Primitives::SetupString(TypeInfo& stringType, SerializeFn serializer)
  {
    stringType.PrimitiveSerializer = serializer;
    stringType.TypeGuid = PrimitiveGuid::String;
  }
  
  // Is this a little endian machine?
  const bool IsLittleEndian()
  {
    // Create a byte-array thats the size of an unsigned short
    byte swapTest[sizeof(ushort)];

    // Set the first two bytes so that, when reinterpreted, their value
    // will indicate whether this is a big or little endian machine
    swapTest[0] = 1;
    swapTest[1] = 0;

    // Reinterpet the value...
    ushort interpretedValue = (*(ushort*)swapTest);

    // This is little endian if the value is 1 (big if it's 256)
    return (interpretedValue == 1);
  }

  // Convert a value to little endian mode
  template <sizeType size>
  void ToLittleEndian(const byte* value, byte* outBuffer)
  {
    // Cache the little-endian check, since it won't change ever
    static const bool isLittleEndian = IsLittleEndian();

    // If this is already a little endian machine...
    if (isLittleEndian)
    {
      // Just copy the value directly into the out buffer
      memcpy(outBuffer, value, size);
    }
    else
    {
      // Swap the byte order and copy it into the out buffer
      for (sizeType i = 0; i < size; ++i)
      {
        outBuffer[(size - 1) - i] = value[i];
      }
    }
  }

  // Convert a value to little endian mode
  template <sizeType size>
  void ToLittleEndianInPlace(byte* value)
  {
    // Cache the little-endian check, since it won't change ever
    static const bool isLittleEndian = IsLittleEndian();

    // If this is Not a little endian machine... (otherwise we don't need to do anything)
    if (!isLittleEndian)
    {
      // Compute the half size (we only need to swap up to half way)
      sizeType halfSize = size / 2;

      // Swap each byte from beginning to end
      for (sizeType index = 0; index < halfSize; ++index)
      {
        // Compute the reverse index
        sizeType reverseIndex = (size - 1) - index;

        // Perform the swap using a temporary
        byte temp = value[index];
        value[index] = value[reverseIndex];
        value[reverseIndex] = temp;
      }
    }
  }

  template <sizeType size>
  void Serialize(void* value, Buffer& buffer, SerializeDirection::Enum direction)
  {
    // If we are writing to the buffer...
    if (direction == SerializeDirection::Writing)
    {
      // Create a temporary buffer to hold the data
      byte tempBuffer[size];

      // Convert the data to little endian form (if it isn't already)
      ToLittleEndian<size>((byte*)value, tempBuffer);

      // Write the data out to the buffer
      buffer.Write(tempBuffer, size);
    }
    // If we are reading from the buffer...
    else
    {
      // Read the data directly into the value itself
      buffer.Read(value, size);

      // Convert the data to little endian form (if it isn't already)
      ToLittleEndianInPlace<size>((byte*)value);
    }
  }

  // Specialization for 1 byte types (since we do not need to perform endian swaps)
  template <>
  void Serialize<sizeof(byte)>(void* value, Buffer& buffer, SerializeDirection::Enum direction)
  {
    // If we are writing to the buffer...
    if (direction == SerializeDirection::Writing)
    {
      // Write the value to the buffer
      buffer.Write(value, sizeof(byte));
    }
    // If we are reading from the buffer...
    else
    {
      // Read the data directly into the value itself
      buffer.Read(value, sizeof(byte));
    }
  }

  // Serialize a boolean value
  void SerializeBool(void* value, Buffer& buffer, SerializeDirection::Enum direction)
  {
    // If we are writing to the buffer...
    if (direction == SerializeDirection::Writing)
    {
      // Convert the value to a bool, then to a single byte
      byte result = (byte)(*(bool*)value);

      // Write the value to the buffer
      buffer.Write(&result, sizeof(byte));
    }
    // If we are reading from the buffer...
    else
    {
      // Store the result when we read it
      byte result;

      // Read the value into the resulting byte
      buffer.Read(&result, sizeof(byte));

      // Now store the result into a bool that was passed in
      (*(bool*)value) = (result != 0);
    }
  }

  // Get the serializer function for a particular type
  void SetupPrimitiveType(TypeInfo& type, sizeType size, PrimitiveTrait::Enum trait)
  {
    // Based on the primitive trait...
    switch (trait)
    {
      // A signed integral primitive type
      case PrimitiveTrait::SignedIntegral:
      {
        // Based on the size...
        switch (size)
        {
          case 1:
            type.TypeGuid = PrimitiveGuid::SByte;
            type.PrimitiveSerializer = Serialize<1>;
            return;
          case 2:
            type.TypeGuid = PrimitiveGuid::Int16;
            type.PrimitiveSerializer = Serialize<2>;
            return;
          case 4:
            type.TypeGuid = PrimitiveGuid::Int32;
            type.PrimitiveSerializer = Serialize<4>;
            return;
          case 8:
            type.TypeGuid = PrimitiveGuid::Int64;
            type.PrimitiveSerializer = Serialize<8>;
            return;
        }
      }

      // An unsigned integral primitive type
      case PrimitiveTrait::UnsignedIntegral:
      {
        // Based on the size...
        switch (size)
        {
          case 1:
            type.TypeGuid = PrimitiveGuid::Byte;
            type.PrimitiveSerializer = Serialize<1>;
            return;
          case 2:
            type.TypeGuid = PrimitiveGuid::UInt16;
            type.PrimitiveSerializer = Serialize<2>;
            return;
          case 4:
            type.TypeGuid = PrimitiveGuid::UInt32;
            type.PrimitiveSerializer = Serialize<4>;
            return;
          case 8:
            type.TypeGuid = PrimitiveGuid::UInt64;
            type.PrimitiveSerializer = Serialize<8>;
            return;
        }
      }

      // A floating point primitive type
      case PrimitiveTrait::FloatingPoint:
      {
        // Based on the size...
        switch (size)
        {
          case 4:
            type.TypeGuid = PrimitiveGuid::Single;
            type.PrimitiveSerializer = Serialize<4>;
            return;
          case 8:
            type.TypeGuid = PrimitiveGuid::Double;
            type.PrimitiveSerializer = Serialize<8>;
            return;
        }
      }

      // A boolean primitive
      case PrimitiveTrait::Boolean:
      {
        // Bools are treated specially since they can vary in size
        type.TypeGuid = PrimitiveGuid::Boolean;
        type.PrimitiveSerializer = SerializeBool;
        return;
      }
    }

    // Issue a warning if we get here, since the type could not be initialized
    SkugoWarning("Unable to automatically assign a type to a primitive.");
  }

  // A helper macro that we use to define all the setup specializations
  #define SkugoDefinePrimitiveSetup(type, trait)          \
    template<>                                            \
    void Primitives::Setup<type>(TypeInfo& typeInfo)      \
    {                                                     \
      SetupPrimitiveType(typeInfo, sizeof(type), trait);  \
    }

  // Specializations for primitive type setup
  SkugoDefinePrimitiveSetup(char,   PrimitiveTrait::SignedIntegral);
  SkugoDefinePrimitiveSetup(short,  PrimitiveTrait::SignedIntegral);
  SkugoDefinePrimitiveSetup(int,    PrimitiveTrait::SignedIntegral);
  SkugoDefinePrimitiveSetup(long,   PrimitiveTrait::SignedIntegral);
  SkugoDefinePrimitiveSetup(int64,  PrimitiveTrait::SignedIntegral);
  SkugoDefinePrimitiveSetup(uchar,  PrimitiveTrait::UnsignedIntegral);
  SkugoDefinePrimitiveSetup(ushort, PrimitiveTrait::UnsignedIntegral);
  SkugoDefinePrimitiveSetup(uint,   PrimitiveTrait::UnsignedIntegral);
  SkugoDefinePrimitiveSetup(ulong,  PrimitiveTrait::UnsignedIntegral);
  SkugoDefinePrimitiveSetup(uint64, PrimitiveTrait::UnsignedIntegral);
  SkugoDefinePrimitiveSetup(float,  PrimitiveTrait::FloatingPoint);
  SkugoDefinePrimitiveSetup(double, PrimitiveTrait::FloatingPoint);
  SkugoDefinePrimitiveSetup(bool,   PrimitiveTrait::Boolean);
}
